Разгледайте useActionState на React с машини на състоянията за изграждане на стабилни и предвидими потребителски интерфейси. Научете логиката за преход между състояния на действие за сложни приложения.
React useActionState State Machine: Овладяване на логиката за преход между състояния на действие
useActionState
на React е мощен hook, въведен в React 19 (понастоящем в canary), предназначен да опрости асинхронните актуализации на състоянието, особено при работа със сървърни действия. Когато се комбинира с машина на състоянията, той предоставя елегантен и стабилен начин за управление на сложни UI взаимодействия и преходи между състояния. Тази публикация в блога ще разгледа как ефективно да използвате useActionState
с машина на състоянията, за да създавате предвидими и лесни за поддръжка React приложения.
Какво е машина на състоянията?
Машината на състоянията е математически модел на изчисление, който описва поведението на система като краен брой състояния и преходи между тези състояния. Всяко състояние представлява различно условие на системата, а преходите представляват събитията, които карат системата да премине от едно състояние в друго. Мислете за това като за блок-схема, но с по-строги правила за това как можете да се движите между стъпките.
Използването на машина на състоянията във вашето React приложение предлага няколко предимства:
- Предвидимост: Машините на състоянията налагат ясен и предвидим поток на контрол, което улеснява разбирането на поведението на вашето приложение.
- Поддръжка: Чрез отделяне на логиката на състоянието от рендирането на UI, машините на състоянията подобряват организацията на кода и го правят по-лесен за поддръжка и актуализация.
- Тестване: Машините на състоянията са лесни за тестване по своята същност, защото можете лесно да дефинирате очакваното поведение за всяко състояние и преход.
- Визуално представяне: Машините на състоянията могат да бъдат визуално представени, което помага при комуникацията на поведението на приложението с други разработчици или заинтересовани страни.
Представяне на useActionState
Hook-ът useActionState
ви позволява да обработвате резултата от действие, което потенциално променя състоянието на приложението. Той е проектиран да работи безпроблемно със сървърни действия, но може да бъде адаптиран и за действия от страна на клиента. Той предоставя чист начин за управление на състояния на зареждане, грешки и крайния резултат от дадено действие, което улеснява изграждането на отзивчиви и лесни за използване потребителски интерфейси.
Ето основен пример за това как се използва useActionState
:
const [state, dispatch] = useActionState(async (prevState, formData) => {
// Вашата логика на действието тук
try {
const result = await someAsyncFunction(formData);
return { ...prevState, data: result };
} catch (error) {
return { ...prevState, error: error.message };
}
}, { data: null, error: null });
В този пример:
- Първият аргумент е асинхронна функция, която извършва действието. Тя получава предишното състояние и данните от формата (ако е приложимо).
- Вторият аргумент е началното състояние.
- Hook-ът връща масив, съдържащ текущото състояние и dispatch функция.
Комбиниране на useActionState
и машини на състоянията
Истинската сила идва от комбинирането на useActionState
с машина на състоянията. Това ви позволява да дефинирате сложни преходи между състояния, задействани от асинхронни действия. Нека разгледаме един сценарий: прост компонент за електронна търговия, който извлича детайли за продукт.
Пример: Извличане на детайли за продукт
Ще дефинираме следните състояния за нашия компонент за детайли на продукта:
- Idle (Неактивно): Началното състояние. Все още не са извлечени детайли за продукта.
- Loading (Зареждане): Състоянието, докато се извличат детайлите на продукта.
- Success (Успех): Състоянието, след като детайлите на продукта са извлечени успешно.
- Error (Грешка): Състоянието, ако е възникнала грешка при извличането на детайлите на продукта.
Можем да представим тази машина на състоянията с помощта на обект:
const productDetailsMachine = {
initial: 'idle',
states: {
idle: {
on: {
FETCH: 'loading',
},
},
loading: {
on: {
SUCCESS: 'success',
ERROR: 'error',
},
},
success: {
type: 'final',
},
error: {
on: {
FETCH: 'loading',
},
},
},
};
Това е опростено представяне; библиотеки като XState предоставят по-сложни имплементации на машини на състоянията с функции като йерархични състояния, паралелни състояния и предпазители (guards).
Имплементация в React
Сега, нека интегрираме тази машина на състоянията с useActionState
в React компонент.
import React from 'react';
// Инсталирайте XState, ако искате пълното изживяване с машина на състоянията. За този основен пример ще използваме прост обект.
// import { createMachine, useMachine } from 'xstate';
const productDetailsMachine = {
initial: 'idle',
states: {
idle: {
on: {
FETCH: 'loading',
},
},
loading: {
on: {
SUCCESS: 'success',
ERROR: 'error',
},
},
success: {
type: 'final',
},
error: {
on: {
FETCH: 'loading',
},
},
},
};
function ProductDetails({ productId }) {
const [state, dispatch] = React.useReducer(
(state, event) => {
const nextState = productDetailsMachine.states[state].on[event];
return nextState || state; // Върни следващото състояние или текущото, ако няма дефиниран преход
},
productDetailsMachine.initial
);
const [productData, setProductData] = React.useState(null);
const [error, setError] = React.useState(null);
React.useEffect(() => {
if (state === 'loading') {
const fetchData = async () => {
try {
const response = await fetch(`https://api.example.com/products/${productId}`); // Заменете с вашия API endpoint
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setProductData(data);
setError(null);
dispatch('SUCCESS');
} catch (e) {
setError(e.message);
setProductData(null);
dispatch('ERROR');
}
};
fetchData();
}
}, [state, productId, dispatch]);
const handleFetch = () => {
dispatch('FETCH');
};
return (
Product Details
{state === 'idle' && }
{state === 'loading' && Loading...
}
{state === 'success' && (
{productData.name}
{productData.description}
Price: ${productData.price}
)}
{state === 'error' && Error: {error}
}
);
}
export default ProductDetails;
Обяснение:
- Дефинираме
productDetailsMachine
като прост JavaScript обект, представляващ нашата машина на състоянията. - Използваме
React.useReducer
за управление на преходите между състоянията въз основа на нашата машина. - Използваме hook-а
useEffect
на React, за да задействаме извличането на данни, когато състоянието е 'loading'. - Функцията
handleFetch
изпраща събитието 'FETCH', инициирайки състоянието на зареждане. - Компонентът рендира различно съдържание в зависимост от текущото състояние.
Използване на useActionState
(Хипотетично - функция в React 19)
Въпреки че useActionState
все още не е напълно достъпен, ето как би изглеждала имплементацията, когато стане налична, предлагайки по-чист подход:
import React from 'react';
//import { useActionState } from 'react'; // Разкоментирайте, когато стане наличен
const productDetailsMachine = {
initial: 'idle',
states: {
idle: {
on: {
FETCH: 'loading',
},
},
loading: {
on: {
SUCCESS: 'success',
ERROR: 'error',
},
},
success: {
type: 'final',
},
error: {
on: {
FETCH: 'loading',
},
},
},
};
function ProductDetails({ productId }) {
const initialState = { state: productDetailsMachine.initial, data: null, error: null };
// Хипотетична имплементация на useActionState
const [newState, dispatch] = React.useReducer(
(state, event) => {
const nextState = productDetailsMachine.states[state.state].on[event];
return nextState ? { ...state, state: nextState } : state; // Върни следващото състояние или текущото, ако няма дефиниран преход
},
initialState
);
const handleFetchProduct = async () => {
dispatch('FETCH');
try {
const response = await fetch(`https://api.example.com/products/${productId}`); // Заменете с вашия API endpoint
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
// Успешно извлечено - изпратете SUCCESS с данните!
dispatch('SUCCESS');
// Запазете извлечените данни в локалното състояние. Не може да се използва dispatch в reducer.
newState.data = data; // Актуализирайте извън диспечера
} catch (error) {
// Възникна грешка - изпратете ERROR със съобщението за грешка!
dispatch('ERROR');
// Съхранете грешката в нова променлива, за да бъде показана в render()
newState.error = error.message;
}
//}, initialState);
};
return (
Product Details
{newState.state === 'idle' && }
{newState.state === 'loading' && Loading...
}
{newState.state === 'success' && newState.data && (
{newState.data.name}
{newState.data.description}
Price: ${newState.data.price}
)}
{newState.state === 'error' && newState.error && Error: {newState.error}
}
);
}
export default ProductDetails;
Важна забележка: Този пример е хипотетичен, тъй като useActionState
все още не е напълно наличен и точният му API може да се промени. Заместих го със стандартния useReducer за изпълнение на основната логика. Въпреки това, намерението е да се покаже как *бихте* го използвали, ако стане наличен и трябва да замените useReducer с useActionState. В бъдеще с useActionState
, този код трябва да работи както е обяснено с минимални промени, като значително опростява асинхронната обработка на данни.
Предимства от използването на useActionState
с машини на състоянията
- Ясно разделение на отговорностите: Логиката на състоянието е капсулирана в машината на състоянията, докато рендирането на UI се обработва от React компонента.
- Подобрена четимост на кода: Машината на състоянията предоставя визуално представяне на поведението на приложението, което го прави по-лесно за разбиране и поддръжка.
- Опростена асинхронна обработка:
useActionState
улеснява обработката на асинхронни действия, намалявайки шаблонния код. - Подобрена възможност за тестване: Машините на състоянията са лесни за тестване по своята същност, което ви позволява лесно да проверите коректността на поведението на вашето приложение.
Напреднали концепции и съображения
Интеграция с XState
За по-сложни нужди от управление на състоянието, обмислете използването на специализирана библиотека за машини на състоянията като XState. XState предоставя мощна и гъвкава рамка за дефиниране и управление на машини на състоянията, с функции като йерархични състояния, паралелни състояния, предпазители и действия.
// Пример с XState
import { createMachine, useMachine } from 'xstate';
const productDetailsMachine = createMachine({
id: 'productDetails',
initial: 'idle',
states: {
idle: {
on: {
FETCH: 'loading',
},
},
loading: {
invoke: {
id: 'fetchProduct',
src: (context, event) => fetch(`https://api.example.com/products/${context.productId}`).then(res => res.json()),
onDone: {
target: 'success',
actions: assign({ product: (context, event) => event.data })
},
onError: {
target: 'error',
actions: assign({ error: (context, event) => event.data })
}
}
},
success: {
type: 'final',
},
error: {
on: {
FETCH: 'loading',
},
},
},
}, {
services: {
fetchProduct: (context, event) => fetch(`https://api.example.com/products/${context.productId}`).then(res => res.json())
}
});
Това предоставя по-декларативен и стабилен начин за управление на състоянието. Уверете се, че сте я инсталирали с: npm install xstate
Глобално управление на състоянието
За приложения със сложни изисквания за управление на състоянието в множество компоненти, обмислете използването на решение за глобално управление на състоянието като Redux или Zustand в комбинация с машини на състоянията. Това ви позволява да централизирате състоянието на вашето приложение и лесно да го споделяте между компоненти.
Тестване на машини на състоянията
Тестването на машини на състоянията е от решаващо значение за гарантиране на коректността и надеждността на вашето приложение. Можете да използвате рамки за тестване като Jest или Mocha, за да пишете единични тестове за вашите машини на състоянията, проверявайки дали те преминават между състоянията както се очаква и обработват правилно различните събития.
Ето един прост пример:
// Пример за Jest тест
import { interpret } from 'xstate';
import { productDetailsMachine } from './productDetailsMachine';
describe('productDetailsMachine', () => {
it('should transition from idle to loading on FETCH event', (done) => {
const service = interpret(productDetailsMachine).onTransition((state) => {
if (state.value === 'loading') {
expect(state.value).toBe('loading');
done();
}
});
service.start();
service.send('FETCH');
});
});
Интернационализация (i18n)
Когато създавате приложения за глобална аудитория, интернационализацията (i18n) е от съществено значение. Уверете се, че логиката на вашата машина на състоянията и рендирането на UI са правилно интернационализирани, за да поддържат множество езици и културни контексти. Обмислете следното:
- Текстово съдържание: Използвайте i18n библиотеки за превод на текстово съдържание въз основа на локала на потребителя.
- Формати за дата и час: Използвайте библиотеки за форматиране на дата и час, съобразени с локала, за да показвате дати и часове в правилния формат за региона на потребителя.
- Валутни формати: Използвайте библиотеки за форматиране на валута, съобразени с локала, за да показвате валутни стойности в правилния формат за региона на потребителя.
- Числови формати: Използвайте библиотеки за форматиране на числа, съобразени с локала, за да показвате числа в правилния формат за региона на потребителя (напр. десетични разделители, разделители за хиляди).
- Оформление отдясно-наляво (RTL): Поддържайте RTL оформления за езици като арабски и иврит.
Като вземете предвид тези аспекти на i18n, можете да гарантирате, че вашето приложение е достъпно и лесно за използване от глобална аудитория.
Заключение
Комбинирането на useActionState
на React с машини на състоянията предлага мощен подход за изграждане на стабилни и предвидими потребителски интерфейси. Чрез отделяне на логиката на състоянието от рендирането на UI и налагане на ясен поток на контрол, машините на състоянията подобряват организацията на кода, поддръжката и възможността за тестване. Въпреки че useActionState
все още е предстояща функция, разбирането как да се интегрират машини на състоянията сега ще ви подготви да се възползвате от предимствата й, когато стане налична. Библиотеки като XState предоставят още по-напреднали възможности за управление на състоянието, улеснявайки обработката на сложна логика на приложението.
Като възприемете машините на състоянията и useActionState
, можете да повишите уменията си за разработка с React и да създавате приложения, които са по-надеждни, лесни за поддръжка и лесни за използване от потребители по целия свят.